在字串的前綴或後綴補字元,是字串處理常見的需求,過去要自行處理,終於在 ES2017 (ES8) 新增了 String.prototype.padStart()
和 String.prototype.padEnd()
,解決常見的需求!本篇來介紹它們,以及在 ECMAScript spec 是如何定義的,並附上 polyfill。
本文同步發表於 Titangene Blog:JavaScript 之旅 (5):String method - padStart & padEnd
「JavaScript 之旅」系列文章發文於:
pad strings 是一個很常見的需求,例如:
final-001.md
、final-066.md
000021
、000456
2020-09-20
、09:05
Error 001: xxx
0010
、0x00FF
那過去和現代是如何處理這個需求?讓我們繼續看下去...
也許你會自己寫個 padStart()
來處理補字元這種字串處理:
function padStart(string, targetLength, padString = ' ') {
return (Array(targetLength).join(padString) + string)
.slice(-targetLength)
}
console.log(padStart('18', 4, '0')); // 0018
或
function padStart(string, targetLength, padString) {
return padString.repeat(Math.max(0, targetLength - string.length)) + string;
}
console.log(padStart('18', 4, '0')); // 0018
或者從 Stack Overflow 找到不錯的解法參考一下直接複製貼上?上面的範例都是我從 Stack Overflow 參考的 XD。
補充一個與
padStart()
有關的 npm 套件:left-pad
,當年發生了一些故事,詳情可參閱:
padStart
和 padEnd
在 ES2017 (ES8) 新增了 String.prototype.padStart()
和 String.prototype.padEnd()
,終於不用自己處理常見的字串處理需求了!
語法:
maxLength
fillString
string.padStart(maxLength [, fillString])
string.padEnd(maxLength [, fillString])
這些 String 方法都會重複 fillString
這個字串多次,直到字串的長度到 maxLength
為止。
String.prototype.padStart()
是將重複的 fillString
字串加在原字串的前面,而 String.prototype.padEnd()
是加在原字串的後面。看一些簡單的範例:
console.log('18'.padStart(4, '0')); // "0018"
console.log('18'.padEnd(4, '0')); // "1800"
console.log('x'.padStart(4, 'ab')); // "abax"
console.log('x'.padEnd(4, 'ab')); // "xaba"
若不使用第二個參數 (即 fillString
),預設會是 " "
(U+0020),也就是 space:
console.log('18'.padStart(4)); // " 18"
console.log('18'.padEnd(4)); // "18 "
若第二個參數為 ''
(空字串),會回傳原字串:
console.log('18'.padStart(4, '')); // "18"
console.log('18'.padEnd(4, '')); // "18"
若原字串的 length >= 第一個參數的值 (即 maxLength
),則會回傳原字串:
console.log('1234'.padStart(2, '0')); // "1234"
console.log('1234'.padEnd(2, '0')); // "1234"
console.log('1234'.padStart(4, '0')); // "1234"
console.log('1234'.padEnd(4, '0')); // "1234"
若想在 Number 型別的值前面補 0,需要先將 Number 型別強制轉型成 String 型別:
let n = 18;
console.log(String(n).padStart(4, '0')); // "0018"
否則會出現 TypeError
的錯誤 (因為 Number 沒有 padStart
和 padEnd
這些 method):
let n = 18;
console.log(n.padStart(4, '0'));
// TypeError: n.padStart is not a function
以下是 String.prototype.padStart()
和 String.prototype.padEnd()
在 spec 中的定義:可以看到兩者的差異不大,但步驟 2 StringPad()
的最後一個傳入參數不一樣
傳入 start
和 end
能幹嘛?我們接續看步驟 2 StringPad()
的定義:在步驟 11 和步驟 12 就是將 fillString
concat 在原字串的前面或後面的關鍵 (看後面的 polyfill 會更好理解)
下面是從 TC39 的 String.prototype.padStart()
和 String.prototype.padEnd()
提案提供的 polyfill 稍做修改的 (看過剛剛的 spec 定義後,應該知道兩者只差一個步驟不同,原本的 polyfill 是將 spec 中的 StringPad()
都在兩者上分別實作,因邏輯可重用,我就將它抽成額外的 function 了):
const RequireObjectCoercible = O => {
if (O === null || typeof O === 'undefined') {
throw new TypeError('"this" value must not be null or undefined');
}
return O;
};
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1;
const ToLength = argument => {
const len = Number(argument);
if (Number.isNaN(len) || len <= 0) {
return 0;
}
if (len > MAX_SAFE_INTEGER) {
return MAX_SAFE_INTEGER;
}
return len;
};
const StringPad = (O, maxLength, fillString, placement) => {
const S = String(O);
const intMaxLength = ToLength(maxLength);
const stringLength = ToLength(S.length);
if (intMaxLength <= stringLength) {
return S;
}
let filler = typeof fillString === 'undefined' ? ' ' : String(fillString);
if (filler === '') {
return S;
}
const fillLen = intMaxLength - stringLength;
while (filler.length < fillLen) {
const fLen = filler.length;
const remainingCodeUnits = fillLen - fLen;
if (fLen > remainingCodeUnits) {
filler += filler.slice(0, remainingCodeUnits);
} else {
filler += filler;
}
}
const truncatedStringFiller = filler.slice(0, fillLen);
if (placement === 'start') {
return truncatedStringFiller + S;
} else {
return S + truncatedStringFiller;
}
}
if (!String.prototype.padStart) {
String.prototype.padStart = function padStart(maxLength, fillString = ' ') {
const O = RequireObjectCoercible(this);
return StringPad(O, maxLength, fillString, 'start');
};
}
if (!String.prototype.padEnd) {
String.prototype.padEnd = function padEnd(maxLength, fillString = ' ') {
const O = RequireObjectCoercible(this);
return StringPad(O, maxLength, fillString, 'end');
};
}
其他 polyfill:
String.prototype.padStart()
String.prototype.padEnd()